package com.zf.nospring.rabbitmq.dead_letter;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.zf.nospring.rabbitmq.utils.RabbitUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.concurrent.TimeoutException;

/**
 * #### 死信的产生
 * 死信是RabbitMQ中的一种消息机制，如果存在一下情况，消息就会变成死信：
 * 1、 消息被否定确认，使用 channel.basicNack 或 channel.basicReject ，并且此时requeue属性被设置为false，即没有重新入队
 * 2、消息在队列的存活时间超过设置的生存时间（TTL）时间，也就是消息过期了，可以是队列的过期时间也可以是消息的过期时间，哪个小就是哪个
 * 3、消息队列的消息数量已经超过最大队列长度，导致消息不能入队
 * <p>
 * #### 死信的去处：
 * 死信消息会被RabbitMQ进行特殊处理：
 * 1、 如果正常队列配置了 死信交换机（"x-dead-letter-exchange"）和死信路由键（"x-dead-letter-routing-key"），
 * 那么产生的死信就会被发送给配置的死信交换机，同时将死信原本的路由键替换成死信路由键配置的值，
 * 再由死信交换机把死信发送到匹配的死信队列，如果没有找到匹配的队列，死信将会被丢弃。
 * 2、如果正常队列只配置了死信交换机"x-dead-letter-exchange"，没有配置死信路由键"x-dead-letter-routing-key"，那么将使用死信原本的路由键，如果配置的死信路由键是 "" 空字符串，那么新的路由键就是空字符。
 * 3、如果死信所属的队列没有配置死信队列的相关信息，则该消息将会被丢弃。
 * <p>
 * #### 配置死信队列的步骤：
 * 1. 声明一个交换机作为死信交换机，没有类型限制，一般时使用 Direct 类型；也可以使用默认交换机。死信交换机也是五种交换机中的一种，只不过被用来处理死信
 * 2. 声明一个队列 作为死信队列，用来接收死信，死信队列也是一个普通队列，只不过被用来处理死信
 * 3. 绑定死信队列和死信交换机，这样 正常队列中的死信就会通过 死信交换机把 死信 发送到 满足路由条件的死信队列中了
 * 4. 声明一个队列 作为正常队列
 * 5. 配置正常队列的死信交换机：通过  "x-dead-letter-exchange" 参数声明正常队列绑定的死信交换机，该队列中的死信会被发送到配置的死信交换机中
 * 6. 配置正常队列的死信路由键：通过 "x-dead-letter-routing-key" 声明正常队列产生的死信发送给 死信交换机 时携带的路由键
 * 7. 正常队列由于某些情况产生了死信
 * 8. 读取死信队列中的死信，然后处理
 */
public class DeadLetterProducer {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = RabbitUtils.getDefaultConnection();
        Channel channel = connection.createChannel();

        // 声明一个交换机作为 死信交换机，这里使用 Direct交换机
        String testDeadExchange = "test_dead_letter_exchange";
        String testDeadRouteKey = "test_dead_letter_route";
        channel.exchangeDeclare(testDeadExchange, BuiltinExchangeType.DIRECT, true, false, null);

        //声明一个队列作为 死信队列
        String testDeadQueue = "test_dead_letter_queue";
        channel.queueDeclare(testDeadQueue, true, false, false, null);

        // 绑定死信队列和死信交换机
        channel.queueBind(testDeadQueue, testDeadExchange, testDeadRouteKey);

        // 声明正常队列，并配置 死信交换机 和 死信的路由键
        String testNormalQueue = "test_normal_queue";
        HashMap<String, Object> queueArgs = new HashMap<>();
        queueArgs.put("x-dead-letter-exchange", testDeadExchange);
        queueArgs.put("x-dead-letter-routing-key", testDeadRouteKey);
        channel.queueDeclare(testNormalQueue, true, false, false, queueArgs);

        //发送消息，使用默认交换机向正常队列发送消息
        for (int i = 1; i <= 10; i++) {
            channel.basicPublish("", testNormalQueue, null, ("你啊" + i).getBytes(StandardCharsets.UTF_8));
        }

        channel.close();
        connection.close();
    }
}
